﻿#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable:6031)

#include <stdio.h>
#include <string.h>

/* 1.字符指针*/
//在指针的类型中我们知道有一种指针类型为字符指针 char*;
//int main()
//{
//	char ch = 'w';
//	char* pc = &ch;
//	*pc = 'b';
//	printf("%c\n", ch);
//	return 0;
//}

//int main()
//{
//	const char* pstr = "abcd";//这里是把一个字符串放到pstr指针变量里了吗？本质是把字符串abcd首字符的地址放到了pstr中。
//	printf("%s\n", pstr);	  //字符串最后默认有\0，打印到d就停止了
//	return 0;
//}

//题目
//int main()
//{
//	char str1[] = "abc";			//str1是数组，存的就是abc这个字符串
//	char str2[] = "abc";
//	
//	const char* str3 = "abc";		//str3是字符指针，指向的是字符a的地址
//	const char* str4 = "abc";
//
//	if (str1 == str2)
//		printf("str1 and str2 are same\n");
//	else
//		printf("str1 and str2 are not same\n");
//
//	if (str3 == str4)
//		printf("str3 and str4 are same\n");
//	else
//		printf("str3 and str4 are not same\n");
//
//	return 0;
//}
//这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域，当几个指针
//指向同一个字符串的时候，他们实际会指向同一块内存。
//但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同，str3和str4相同。


/* 2. 指针数组*/
//指针数组是指针还是数组？
//答案：是数组。是存放指针的数组。
//数组我们已经知道整形数组，字符数组。那指针数组是怎样的？

//int main()
//{
//	int a = 10;
//	int b = 20;
//	int c = 30;
//
//	//int arr[10];
//
//	int* pa = &a;	
//	int* pb = &b;
//	int* pc = &c;
//
//	int* parr[10] = {&a, &b, &c};	//parr就是存放整型指针的数组-指针数组
//
//	int i = 0;
//	for (i = 0; i < 3; i++)	//0 1 2
//	{
//		printf("%d ", *(parr[i]));	//parr[0]就是&a,在解引用就是a
//	}
//
//	return 0;
//}

//可用指针数组模拟出二维数组
//int main()
//{
//	//int arr[3][4] = { 1,2,3,4, 2,3,4,5, 3,4,5,6 };
//	////1 2 3 4
//	////2 3 4 5
//	////3 4 5 6
//	//int i = 0;
//	//int j = 0;
//	//for (i = 0; i < 3; i++)
//	//{
//	//	for (j = 0; j < 4; j++)
//	//	{
//	//		printf("%d ", arr[i][j]);
//	//	}
//	//	printf("\n");
//	//}
//
//	int arr1[4] = { 1,2,3,4 };
//	int arr2[4] = { 2,3,4,5 };
//	int arr3[4] = { 3,4,5,6 };
//
//	int* parr[3] = { arr1, arr2, arr3 };
//	int i = 0;
//	for (i = 0; i < 3; i++)
//	{
//		int j = 0;
//		for (j = 0; j < 4; j++)
//		{
//			printf("%d ", parr[i][j]);		//parr[i][j]也可写为*(parr[i]+j)
//		}
//		printf("\n");
//	}
//
//	return 0;
//}

//若用C语言实现简单数据结构，指针+结构体+动态内存管理必须学得很好


/* 3. 数组指针*/

//3.1数组指针是指针？还是数组？
//答案是：指针。
//我们已经熟悉：
//整型指针： int* pint; 存放整型类型的地址。
//浮点型指针： float* pf; 存放浮点型类型的地址。
//那数组指针应该是：存放数组的地址。
//下面代码哪个是数组指针？
//int* p1[10];	 int(*p2)[10];
//p1是指针数组
//int (*p)[10]; 解释：p先和*结合，说明p是一个指针变量，然后指着指向的是一个大小为10个整型的数组。所以p是一个指针，指向一个数组，叫数组指针。
//这里要注意：[]的优先级要高于*号的，所以必须加上（）来保证p先和*结合。  此处的*不是解引用，代表p是指针变量

//3.2 &数组名VS数组名
//一维数组的数组名理解
//int main()
//{
//	int arr[10] = { 1,2,3,4,5 };
//	printf("%p\n", arr);		//数组名在此处就是首元素的地址
//	printf("%p\n", arr+1);		//首元素的地址+1可理解为首元素+1，跳到第二个元素；第一个元素的地址与第二个元素的地址相差四个字节
//
//	printf("%p\n", &arr[0]);	//首元素的地址
//	printf("%p\n", &arr[0]+1);	//首元素的地址+1可理解为首元素+1，跳到第二个元素；第一个元素的地址与第二个元素的地址相差四个字节
//
//	printf("%p\n", &arr);		//取出的是整个数组的地址，与首元素的地址只是值相同，但意义不同
//	printf("%p\n", &arr+1);		//数组的地址+1后直接跳过40个字节
//	//所以整个数组的地址和数组首元素的地址还是有区别
//	
//	int n = sizeof(arr);		//若数组名是首元素地址，地址是4个字节(32位<->x86)或者8个字节(64位<->x64)
//	printf("%d\n", n);			//结果应该是4或8，但打印出来后是40
//	
//	return 0;		
//}

// 总结：数组名本质上是数组首元素的地址（除了以下两种情况表示整个数组）
// 1. sizeof(数组名)，sizeof内部单独放一个数组名，这里的数组名表示整个数组，计算的是整个数组的大小（Byte）
// 2. &数组名，这里的数组名表示整个数组，取出的是整个数组的地址，与首元素的地址只是值相同，但意义不同

//再次讨论
//int main()
//{
//	int arr[10] = { 0 };
//	int* p1 = arr;		//arr在此处就是首元素的地址，首元素是int类型的，所以指针类型是int*，然后用指针变量p1存起来
//	int (*p2)[10] = &arr;		//&arr取出的是整个数组的地址,与首元素的地址值相同,p2的指针类型该怎么写？--p2是存放数组地址的指针，所以p2是数组指针
////！！！！！！！！！！！！！！！！！！！！！！！！！！！
//	//数组指针p2的类型分析：*就代表p2是指针，右边有[]就是指向数组，指向的数组有10个元素，元素类型是int，所以最前面写int。就是int (* )[10]
////！！！！！！！！！！！！！！！！！！！！！！！！！！！
//	//二级指针是用来存放一级指针变量的地址的，&arr不是一级指针变量的地址，所以指针类型不能写成int**
//	return 0;
//}

//3.3 数组指针的使用
//int main()
//{
//	char* arr[5] = { 0 };		//arr是存放字符指针的数组-指针数组
//	char* (*pc)[5] = &arr;		//数组指针pc的类型分析：*就代表pc是指针，右边有[]就是指向数组，指向的数组有5个元素，元素类型是char*，所以最前面写char*。就是char* (* )[5]
//
//	return 0;
//}

//int main()
//{
//	//int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	//int (*p)[10] = &arr;		//把数组arr的地址赋值给数组指针变量p，但是我们一般很少这样写代码
//	////通过数组指针打印数组的10个元素--不合适的方法
//	//int i = 0;
//	//int sz = sizeof(arr) / sizeof(arr[0]);
//	//for (i = 0; i < sz; i++)
//	//{
//	//	printf("%d ", *(*p + i));		//p指向arr这个数组，*p就相当于数组名arr，数组名又是数组首元素地址，所以*p本质上是数组首元素地址
//	//}									//*p + i就相当于下标为i元素的地址，再解引用就找到了元素
//
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };//这是更合适的方法
//	int* p = arr;						//此处的数组名就是首元素的地址
//	int i = 0;
//	for (i = 0; i < 10; i++)
//	{
//		printf("%d ", *(p + i));		//指针变量p指向首元素的地址，p+i就指向其他元素的地址，在解引用就能找到地址下的内容
//	}
//
//	return 0;
//}

//数组指针使用在二维或三维数组上会更好
//打印二维数组
//void print_arr1(int arr[3][5], int row, int col)		//这种写法会误以为传过来的是整个arr数组，但实际上传的是数组首元素的地址
//{
//	int i = 0;
//	for (i = 0; i < row; i++)
//	{
//		int j = 0;
//		for (j = 0; j < col; j++)
//		{
//			printf("%d ", arr[i][j]);
//		}
//		printf("\n");
//	}
//	printf("\n");
//}
//
//void print_arr2(int (*p)[5], int row, int col)	//这种写法更直观,但注意二维数组的首元素是第一行，首元素的地址就是第一行的地址，就是一个一维数组的地址
//{														
//	int i = 0;
//	for (i = 0; i < row; i++)
//	{
//		int j = 0;
//		for (j = 0; j < col; j++)
//		{
//			printf("%d ", *(*(p + i) + j));		//p+i就指向二维数组的第i行的地址，再解引用就拿到了这一行，就相当于拿到了这一行的数组名(arr[i])，数组名又是首元素的地址，所以*(p+i)就是指向第i行的首元素地址。   *(p+i)<==>arr[i]
//		}										//*(p+i)+j就是第i行下标为j的元素的地址，再解引用，就拿到了最终的元素    *( *(p+i)+j) )<==>arr[i][j]
//		printf("\n");
//	}
//} 
//
//int main()
//{
//	int arr[3][5] = { 1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7 };
//	print_arr1(arr, 3, 5);
//	//数组名arr，表示首元素的地址
//	//但是二维数组的首元素是二维数组的第一行，所以这里传递的arr，其实相当于第一行的地址，就是一维数组的地址，可以用数组指针p来接收.
//	//此时p就指向二维数组第一行的地址，p+i就指向二维数组的第i行的地址
//	print_arr2(arr, 3, 5);
//	return 0;
//}

//int arr[5];				//arr是一个整型数组
//int* parr1[10];			//parr1是一个整型指针数组
//int (*parr2)[10];			//parr2是一个数组指针
//int (*parr3[10])[5];		//parr3[10]是一个数组，有10个元素，每个元素是一个 int (* )[5] 类型的数组指针，各自指向一个有5个int型元素的数组。parr3是存放数组指针的数组


/* 4. 数组参数、指针参数*/
//在写代码的时候难免要把【数组】或者【指针】传给函数，那函数的参数该如何设计呢？

//4.1 一维数组传参
//void test1(int arr[])//ok
//{}
//void test1(int arr[10])//ok
//{}
//void test1(int* arr)//ok
//{}
//void test2(int* arr[20])//ok 形参可写成数组的形式
//{}
//void test2(int** arr)//ok 形参写成指针的形式  数组名arr2就是首元素的地址，就是int*类型的地址，接收一级指针变量的地址可用二级指针************************************
//{}
//
//int main()
//{
//	int arr[10] = { 0 };
//	int* arr2[20] = { 0 };		//arr2是一个指针数组，有20个元素，每个元素都是int*
//	test1(arr);
//	test2(arr2);		
//}

//4.2 二维数组传参
//void test(int arr[3][5])//ok
//{}
//void test(int arr[][])	//no
//{}
//void test(int arr[][5])	//ok
//{}
////总结：二维数组传参，函数形参的设计只能省略第一个[]的数字。因为对一个二维数组，可以不知道有多少行，但是必须知道一行多少元素。
//
//void test(int* arr)		//no	因为要接收一维数组的地址，所以要用数组指针
//{}
//void test(int* arr[5])	//no	arr[5]还是数组
//{}
//void test(int (*arr)[5])//ok    此处的arr就是数组指针
//{}
//void test(int** arr)	//no    二级指针是接收(存放)一级指针变量的地址，不是一维数组的地址
//{}
////注意：二维数组的首元素是二维数组的第一行，所以主函数中传递的arr，其实相当于第一行的地址，就是一维数组的地址，接收一维数组的地址要用数组指针
//
//int main()
//{
//	int arr[3][5] = { 0 };
//	test(arr);		
//}

//4.3 一级指针传参
//void print(int* p, int sz)		//指针传参时，用指针接收即可
//{								//注意此处接收的只是指针，不是指针的地址，不能用二级指针来接收
//	int i = 0;
//	for (i = 0; i < sz; i++)
//	{
//		printf("%d\n", *(p + i));
//	}
//}
//
//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9 };
//	int* p = arr;
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	//一级指针p，传给函数
//	print(p, sz);
//	return 0;
//}

//4.4 二级指针传参
//void test(int** ptr)
//{
//	printf("num = %d\n", **ptr);
//}
//
//int main()
//{
//	int n = 10;
//	int* p = &n;
//	int** pp = &p;
//	test(pp);		//将二级指针进行传参，拿二级指针进行接收没问题
//	test(&p);
//	return 0;
//}


/* 5. 函数指针*/
//类比数组指针：指向(存放)数组的地址(指针)
//函数指针：指向函数的地址

//int Add(int x, int y)
//{
//	return x + y;
//}
//
//int main()
//{
//	char* arr[5] = { 0 };		//arr是存放字符指针的数组-指针数组
//	char* (*pc)[5] = &arr;		//数组指针pc的类型分析：*就代表pc是指针，右边有[]就是指向数组，指向的数组有5个元素，元素类型是char*，所以最前面写char*。就是char* (* )[5]
//
//	//&数组名-取出整个数组的地址
//	//&函数名-取出整个函数的地址
//
//	printf("%p\n", &Add);
//	printf("%p\n", Add);		//对于函数来说，参数为&函数名或函数名无区别，都可取出函数的地址
//
//	//存储函数的地址
//	int (*pf)(int, int) = &Add;//函数指针pf的类型分析：*就代表pf是指针，右边有()就是指向函数，函数的参数是int,int，返回值类型也是int,照着写即可
//	int ret = (*pf)(2, 3);		//*pf就相当于找到了Add函数,再传参即可方便修改   
//	int rez = pf(1, 3);			//*也可以不写
//	printf("%d\n", ret);
//	printf("%d\n", rez);
//
//	return 0;
//}

//其他说明
//int Add(int x, int y)
//{
//	return x + y;
//}
//
//void calc(int (*pf)(int,int))	//接收函数的地址用函数指针接收，函数指针的类型由函数的参数和返回值类型决定
//{
//	int a = 3;
//	int b = 5;
//	int ret = pf(a, b);
//	printf("%d\n", ret);
//}
//
//int main()
//{
//	calc(Add);		//函数名就是函数的地址
//	
//	return 0;
//}

//函数指针的用途-写一个就计算器，实现+ - * /
//设计：选1就是+,选2就是- ........

//void menu()
//{
//	printf("*****************************\n");
//	printf("******  1.add  2.sub  *******\n");
//	printf("******  3.mul  4.div  *******\n");
//	printf("******  0.exit        *******\n");
//	printf("*****************************\n");
//}
//
//int Add(int x, int y)
//{
//	return x + y;
//}
//
//int Sub(int x, int y)
//{
//	return x - y;
//}
//
//int Mul(int x, int y)
//{
//	return x * y;
//}
//
//int Div(int x, int y)
//{
//	return x / y;
//}
//
//void calc(int (*pf)(int, int))		//接收函数的地址应用函数指针
//{
//	int x = 0;
//	int y = 0;
//	int ret = 0;
//	printf("请输入2个操作数:>");
//	scanf("%d %d", &x, &y);
//	ret = pf(x, y);
//	printf("%d\n", ret);
//}
//
//int main()
//{
//	int input = 0;
//
//	do
//	{
//		menu();
//		printf("请选择:>");
//		scanf("%d", &input);
//		switch (input)
//		{
//		case 1:
//			//printf("请输入2个操作数:>");
//			//scanf("%d %d", &x, &y);
//			//ret = Add(x, y);
//			//printf("%d\n", ret);
//			calc(Add);
//			break;
//		case 2:
//			//printf("请输入2个操作数:>");
//			//scanf("%d %d", &x, &y);
//			//ret = Sub(x, y);
//			//printf("%d\n", ret);
//			calc(Sub);
//			break;
//		case 3:
//			//printf("请输入2个操作数:>");
//			//scanf("%d %d", &x, &y);
//			//ret = Mul(x, y);
//			//printf("%d\n", ret);
//			calc(Mul);
//			break;
//		case 4:
//			//printf("请输入2个操作数:>");
//			//scanf("%d %d", &x, &y);
//			//ret = Div(x, y);
//			//printf("%d\n", ret);
//			calc(Div);
//			break;
//		case 0:
//			printf("退出");
//			break;
//		default:
//			printf("选择错误");
//			break;
//		}
//	} while (input);
//
//	return 0;
//}
//代码冗余，可用函数指针来解决


//分析两段有趣的代码：

//代码1		(  *(void (*)())0	)();		
//			void (*)()是一个函数指针类型，外面有一对()说明要强制类型转换0，0又是int型。==>将0强制转换成函数指针类型，就认为0是一个地址
//			所以(void (*)())0就是函数的地址，前面的*就是解引用,找到这个函数，最右边的()就是调用，但不传参
//			突破点就是0，以上代码就是一次函数调用
	
//代码2		void ( *signal(int, void(*)(int)) )(int);
//			signal(int, void(*)(int))是一个返回值为void，参数为int的函数指针--❌，若是函数指针要和*结合，用()括起来
//			signal首先和(结合，signal一定是函数名，里面的参数一个是整型类型，一个是函数指针类型，形参被省略，所以signal(int, void(*)(int))是一个函数声明
//			剩下的void (* )(int)又是一个函数指针类型，所以signal函数的返回类型也是一个函数指针类型
//			突破点就是signal，以上代码就是一个函数声明
//	简化此代码：
//	typedef void (* )(int) pf_t;		//注意对一个函数指针类型重定义时，新名字要放在*旁边
typedef void (*pf_t)(int);				//把void (* )(int)重定义为pf_t
pf_t signal(int, pf_t);


/* 6. 函数指针数组*/
//指针数组 int* arr[10]={0};  char* arr[5]={0};
//把函数指针放在一个数组中，这个数组就是该函数指针数组

//int Add(int x, int y)
//{
//	return x + y;
//}
//
//int Sub(int x, int y)
//{
//	return x - y;
//}
//
//int Mul(int x, int y)
//{
//	return x * y;
//}
//
//int Div(int x, int y)
//{
//	return x / y;
//}
//
//int main()
//{
//	int (*pf)(int, int) = Add;		//pf是函数指针，接收函数的地址
//	//Add Sub Mul Div就是函数的地址。因为这四个函数的类型一样，函数的地址的类型也是一样的，就可把这四个函数地址(函数名)放在一个数组中
//	int (*arr[4])(int, int) = { Add, Sub ,Mul ,Div };		//arr和[先结合说明arr是个数组，有4个元素，每个元素的类型就是int (* )(int, int)，就是函数指针的类型
//															//那么右边{}中就可以放函数指针
//	//如何调用此数组
//	int i = 0;
//	for (i = 0; i < 4; i++)
//	{
//		int ret = arr[i](8, 4);		//arr[0]就是访问Add（arr[0]=Add），arr[1]就是访问Sub....,就是得到了函数的地址，若想通过函数的地址找到函数，可以解引用，也可以不解引用
//									//函数找到后再调用()即可
//		printf("%d\n", ret);
//	}
//
//	return 0;
//}

//使用函数指针数组实现计算器
//int add(int a, int b)
//{
//	return a + b;
//}
//int sub(int a, int b)
//{
//	return a - b;
//}
//int mul(int a, int b)
//{
//	return a * b;
//}
//int div(int a, int b)
//{
//	return a / b;
//}
//void menu()
//{
//	printf("*****************************\n");
//	printf("******  1.add  2.sub  *******\n");
//	printf("******  3.mul  4.div  *******\n");
//	printf("******  0.exit        *******\n");
//	printf("*****************************\n");
//}
//
//int main()
//{
//	int x, y;
//	int input = 1;
//	int ret = 0;
//	int (*p[5])(int , int) = { 0, add, sub, mul, div }; //p就是一个函数指针数组；又称转移表
//
//	while (input)
//	{
//		menu();
//		printf("请选择：");
//		scanf("%d", &input);
//
//		if ((input <= 4 && input >= 1))
//		{
//			printf("输入操作数：");
//			scanf("%d %d", &x, &y);
//			ret = (*p[input])(x, y);		//(*p[input])(x, y)不加*也可
//		}
//		else
//			printf("输入有误\n");
//		printf("ret = %d\n", ret);
//	}
//
//	return 0;
//}


/* 7.  指向函数指针数组的指针*/
//void test(const char* str)
//{
//	printf("%s\n", str);
//}
//
//int main()
//{
//	int (*p[5])(int , int) = { 0, add, sub, mul, div }; //p就是一个函数指针数组
//	int (*(*pr)[5])(int, int) = &p;		//对数组取地址取出的是整个数组的地址，要用数组指针接收。pr就是一个指向函数指针数组的指针
//	//pr首先和*结合说明是一个指针，右边有一个[，说明指向数组，数组中的元素类型是int (* )(int, int)，就是函数指针
//
//	return 0;
//}


/* 8.  回调函数*/
//回调函数就是一个通过函数指针调用的函数。如果你把函数的指针（地址）作为参数传递给另一个函数，当
//这个指针被用来调用其所指向的函数时，我们就说这是回调函数。回调函数不是由该函数的实现方直接调用，
//而是在特定的事件或条件发生时由另外的一方调用的，用于对该事件或条件进行响应。

//冒泡排序：只能排序整型数据
//冒泡排序的算法：对数组中的元素进行排序
//冒泡排序的思想：两个相邻的元素进行比较；一趟冒泡排序让一个数据来到它最终应该出现的位置上；n个元素就需要n-1趟冒泡排序

//优化--置标志位flag
//void bubble_sort(int* arr,int sz)		//地址要用指针来接收，所以此处的arr是指针变量，指向传过来数组首元素的地址
//{										//所以此处的sizeof(arr)计算的不是一个数组的大小，而是一个指针的大小—4Byte(32位<->X86)
//	//int sz = sizeof(arr) / sizeof(arr[0]);		//确定趟数前，先确定元素个数
//	int i = 0;
//	for (i = 0; i <sz - 1; i++)
//	{
//		int flag = 1;	//假设数组已经排好序
//		int j = 0;		//下标
//		for (j = 0; j <sz-i-1 ;j++)		//一套内部要进行多少次比较——9次；下一次就是8次 ...所以是个循环
//		{
//			if (arr[j] > arr[j + 1])    //注意排序不同类型的数据时，其比较方式不一样，就可把比较函数的功能提取出来放在qsort函数的第四个变量里
//			{
//				int tmp = arr[j];
//				arr[j] = arr[j + 1];
//				arr[j + 1] = tmp;
//				flag = 0;	//说明交换了数据
//			}
//		}
//		if (flag == 1)
//		{
//			break;
//		}
//	}
//}
//
//int main()
//{
//	//把数组的元素排成升序：0 1 2 3 4 5 6 7 8 9
//	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	bubble_sort(arr,sz);		//数组传参时给一个数组名即可，不用加[]；此处的数组名就是首元素地址
//	int i = 0;
//
//	for (i = 0; i < sz; i++)
//	{
//		printf("%d ", arr[i]); //当i等于10的时候，越界访问了
//	}
//
//	return 0;
//}
//不能在分函数内部求元素个数，因为传过来的是首元素的地址，而不是整个数组

//利用qsort函数实现排序
//qsort是使用快速排序的思想实现的一个排序函数
//qsort函数可以排序任意类型的数据
void qsort( void* base,     //base是要排序数据的起始位置
            size_t num,     //待排序数据元素的个数
            size_t width,   //待排序数据元素的大小(单位是Byte)
            int (*cmp)(const void* e1, const void* e2)       //第四个参数cmp是一个函数指针，接收比较函数的地址。
           );                                                //函数指针的类型是int (* )(const void* e1, const void* e2) ==> 那么其指向函数的参数就是const void* e1, const void* e2；返回值类型就是int
                                                             //e1指向比较的第一个元素，e2指向比较的第二个元素；e1和e2是其比较两个元素的地址

int int_cmp(const void* p1, const void* p2)
{
    return ( *(int*)p1 - *(int*)p2 );       //void*的指针p1,p2是不能直接进行解引用操作的
                                            //所以要先强制类型转换为整型指针：(int*)p1，再解引用
                                            //若左>右就返回大于0的数字；=就返回0；小于就返回小于0的数字
}

struct Stu
{
    char name[20];
    int age;
};

int cmp_stu_by_name(const void* e1, const void* e2)
{
    //strcmp函数的返回值正是：>0 ==0 <0
    return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}

int cmp_stu_by_age(const void* e1, const void* e2)
{
    return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}

void Swap(char* buf1, char* buf2, int width)
{
    int i = 0;
    for (i = 0; i < width; i++)
    {
        char tmp = *buf1;
        *buf1 = *buf2;
        *buf2 = tmp;
        buf1++;
        buf2++;
    }
}

void bubble_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
    int i = 0;
    //趟数
    for (i = 0; i < sz - 1; i++)
    {
        int flag = 1;//假设数组是排好序
        //一趟冒泡排序的过程
        int j = 0;
        for (j = 0; j < sz - 1 - i; j++)
        {
            if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)        //cmp的两个参数是待比较2个元素的地址
            {
                //交换
                Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
                flag = 0;
            }
        }
        if (flag == 1)
        {
            break;
        }
    }
}

int main()
{
    int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
    int i = 0;

    //使用qsort排序整型
    qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);

    for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");

    //使用qsort排序结构体数据
    struct Stu s[] = { {"zhangsan", 15}, {"lisi", 30}, {"wangwu", 25} };
    int sz = sizeof(s) / sizeof(s[0]);
    qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
    qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);

    //采用冒泡的方式模拟实现qsort
    struct Stu s[] = { {"zhangsan", 15}, {"lisi", 30}, {"wangwu", 25} };
    int sz = sizeof(s) / sizeof(s[0]);
    bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name);
    //bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age);

    return 0;
}

//void*的指针p1,p2是不能直接进行解引用操作的，那void*的指针有何用？
//void*类型的指针是一种无具体类型的指针，可以接收任意类型的地址
//因为void*类型的指针是一种无具体类型的指针，所以不能解引用操作，也不能+-整数
//所以当传过来的地址不知道是什么类型时，就可以用void*
//int main()
//{
//    int a = 10;
//    char* pa = &a;      //&a的类型应是int*的，不能用char*的指针接收
//    void* pl = &a;      //ok
//}
